#! /usr/bin/python

import sys
import json
import time
import base64
from twisted.internet import reactor
from twisted.web import server, resource
from twisted.python import log
from datetime import datetime
from pymongo import Connection

try:
    c = Connection()
    db = c.test
except:
    print "Can't connect to MongoDB!"
    sys.exit(1)


class Auth():
    pwdb = {}

    def __init__(self, filename):
        ''' Read username:password dictionary from given file '''
        conffile = open(filename, 'r')
        self.pwdb = json.loads(conffile.read())

    def auth(self, user, password):
        ''' trying to find user:password pair in dictionary '''
        try:
            if self.pwdb[user] == password:
                return True
            else:
                return False
        except:
            return False

    def ReturnError(self, request):
        request.setHeader('WWW-authenticate', 'basic realm="default"')
        return resource.ErrorPage(status=401,
                                  brief="Unauthorized",
                                  detail="401 Authentication required"
                                  ).render(request)

try:
    LocalAuth = Auth("pwdb.json")
except:
    print "Can't initialize password database!"
    sys.exit(1)


class Root(resource.Resource):

    def render_GET(self, request):
        '''
        get response method for the root resource
        localhost:8000/
        '''
        return 'Welcome to the REST API'

    def getChild(self, name, request):
        '''
        We overrite the get child function so that we can handle invalid
        requests
        '''

        if name == '':
            return self
        else:
            if name in VIEWS.keys():
                return resource.Resource.getChild(self, name, request)
            else:
                return PageNotFoundError()


class ListTags(resource.Resource):

    def render_GET(self, request):
        '''
        get response method for the ListTags resource
        hostname:port/ListTags
        '''
        if not LocalAuth.auth(request.getUser(), request.getPassword()):
            return LocalAuth.ReturnError(request)

        log.msg('Processing ListTags request')
        try:
            reply = []
            tags = list(db.tags.find())
            for tag in tags:
                reply.append(tag['tagname'])
            return json.dumps(reply)
        except:
            log.err()
            return resource.ErrorPage(status=500,
                                      brief='Error listing tags!',
                                      detail='').render(request)


class TagInfo(resource.Resource):

    def render_GET(self, request):
        '''
        get response method for the TagInfo resource
        hostname:port/TagInfo
        '''
        if not LocalAuth.auth(request.getUser(), request.getPassword()):
            return LocalAuth.ReturnError(request)

        try:
            log.msg('Processing TagInfo request for tag %s'
                     % (request.args['tag'][0]))
            tag = db.tags.find_one({"tagname": request.args['tag'][0]})
            result = {}
            for key, value in tag.items():
                if type(value) in [int, float, unicode]:
                    result[key] = value
            return json.dumps(result)
        except:
            log.err()
            return resource.ErrorPage(status=500,
                                      brief='Error showing taginfo!',
                                      detail='').render(request)


class ListSubscriptions(resource.Resource):

    def render_GET(self, request):
        '''
        get response method for the ListSubscriptions resource
        hostname:port/ListSubscriptions
        '''
        if not LocalAuth.auth(request.getUser(), request.getPassword()):
            return LocalAuth.ReturnError(request)

        try:
            log.msg('Processing ListSubscriptions for user %s'
                    % request.getUser())
            reply = {}
            print {"user": request.getUser()}
            subscriptions = list(db.
                                 subscriptions.
                                 find({"user": request.getUser()}))
            for subscription in subscriptions:
                reply[subscription['tagname']] = {'readonly': subscription['readonly']}
            return json.dumps(reply)
        except:
            log.err()
            return resource.ErrorPage(status=500,
                                      brief='Error showing subscriptions!',
                                      detail='').render(request)


class ListFiles(resource.Resource):

    def render_GET(self, request):
        '''
        get response method for the ListTags resource
        hostname:port/ListFiles
        '''
        if not LocalAuth.auth(request.getUser(), request.getPassword()):
            return LocalAuth.ReturnError(request)

        try:
            log.msg('Processing ListFiles request for tag %s'
                     % (request.args['tag'][0]))
            reply = {}
            files = list(db.files.find({"tagname": request.args['tag'][0]}))
            for file1 in files:
                reply[file1['filename']] = {'client': file1['client'],
                                            'mtime': file1['mtime'],
                                            'size': file1['size'],
                                            'removed': file1['removed']}
            return json.dumps(reply)
        except:
            log.err()
            return resource.ErrorPage(status=500,
                                      brief='Error listing files!',
                                      detail='').render(request)


class Subscribe(resource.Resource):

    def render_POST(self, request):
        '''
        post response method for the Subscribe resource
        hostname:port/Subscribe
        '''
        if not LocalAuth.auth(request.getUser(), request.getPassword()):
            return LocalAuth.ReturnError(request)

        try:
            log.msg('Processing Subscribe request for tag %s \
                     for user %s with readonly=%s flag'
                    % (request.args['tag'][0], request.getUser(), request.args['readonly'][0]))
            readonly = False
            if request.args['readonly'][0].lower() == 'true':
                readonly = True
            reply = []
            subscriptions = list(db.
                                 subscriptions.
                                 find({"tagname": request.args['tag'][0],
                                       "user": request.getUser()}))
            if subscriptions == []:
                db.subscriptions.insert({"tagname": request.args['tag'][0],
                                         "user": request.getUser(),
                                         "readonly": readonly})
            else:
                db.subscriptions.update({"tagname": request.args['tag'][0],
                                         "user": request.getUser()},
                                        {"$set": {"readonly": readonly}}
                                        )

            return json.dumps(reply)
        except:
            log.err()
            return resource.ErrorPage(status=500,
                                      brief='Error while subscribing!',
                                      detail='').render(request)


class Unsubscribe(resource.Resource):

    def render_POST(self, request):
        '''
        post response method for the Unsubscribe resource
        hostname:port/Unubscribe
        '''
        if not LocalAuth.auth(request.getUser(), request.getPassword()):
            return LocalAuth.ReturnError(request)

        try:
            log.msg('Processing Unsubscribe request for tag %s for user %s'
                    % (request.args['tag'][0], request.getUser()))
            reply = []
            subscriptions = db.subscriptions.find({"tagname":
                                                   request.args['tag'][0],
                                                   "user":
                                                   request.getUser()})
            for subscription in subscriptions:
                db.subscriptions.remove(subscription)

            return json.dumps(reply)
        except:
            log.err()
            return resource.ErrorPage(status=500,
                                      brief='Error while subscribing!',
                                      detail='').render(request)


class Get(resource.Resource):

    def render_GET(self, request):
        '''
        get response method for the Get resource
        hostname:port/Get
        '''
        if not LocalAuth.auth(request.getUser(), request.getPassword()):
            return LocalAuth.ReturnError(request)

        try:
            log.msg('Processing Get request for tag %s filename %s'
                    % (request.args['tag'][0], request.args['file'][0]))
            file1 = db.files.find_one({"tagname": request.args['tag'][0],
                                       "filename": request.args['file'][0]})
            result = {}
            for key, value in file1.items():
                if type(value) in [int, float, unicode, bool]:
                    result[key] = value
            return json.dumps(result)
        except:
            log.err()
            return resource.ErrorPage(status=500,
                                      brief='Error getting file!',
                                      detail='').render(request)


class Put(resource.Resource):

    def render_POST(self, request):
        '''
        post response method for the Put resource
        hostname:port/Put
        '''
        if not LocalAuth.auth(request.getUser(), request.getPassword()):
            return LocalAuth.ReturnError(request)

        try:
            log.msg('Processing Put request for file %s tag %s'
                    % (request.args['file'][0], request.args['tag'][0]))
            reply = []
            tag = list(db.tags.find({"tagname": request.args['tag'][0]}))

            if tag != []:
                size = len(base64.b64decode(request.args['content'][0]))
                files = list(db.files.find({"tagname":
                                            request.args['tag'][0],
                                            "filename":
                                            request.args['file'][0]}))
                if files == []:
                    db.files.insert({"tagname": request.args['tag'][0],
                                     "filename": request.args['file'][0],
                                     "mtime": float(request.args['mtime'][0]),
                                     "content": request.args['content'][0],
                                     "update": time.time(),
                                     "size": size,
                                     "client": request.getUser(),
                                     "removed" : False})
                else:
                    db.files.update({"tagname": request.args['tag'][0],
                                     "filename": request.args['file'][0]},
                                    {"$set": {"mtime":
                                              float(request.args['mtime'][0]),
                                              "content":
                                              request.args['content'][0],
                                              "update": time.time(),
                                              "size": size,
                                              "client": request.getUser(),
                                              "removed" : False}})
                db.tags.update({"tagname": request.args['tag'][0]},
                                {"$set": {"update": time.time()}})
            else:
                return resource.ErrorPage(status=412,
                                          brief='No such tag!',
                                         detail='No such tag %s !' %
                                         request.args['tag'][0]
                                         ).render(request)
            return json.dumps(reply)
        except:
            log.err()
            return resource.ErrorPage(status=500,
                                      brief='Error while putting file!',
                                      detail='').render(request)


class Delete(resource.Resource):

    def render_POST(self, request):
        '''
        post response method for the Delete resource
        hostname:port/Delete
        '''
        if not LocalAuth.auth(request.getUser(), request.getPassword()):
            return LocalAuth.ReturnError(request)

        try:
            log.msg('Processing Delete request for file %s tag %s'
                    % (request.args['file'][0], request.args['tag'][0]))
            mtime = float(request.args['mtime'][0])
            reply = []
            files = db.files.find_one({"tagname": request.args['tag'][0],
                                       "filename": request.args['file'][0]})
            if files != []:
                db.files.update({"tagname": request.args['tag'][0],
                                     "filename": request.args['file'][0]},
                                    {"$set": {"removed" : True,
                                              "mtime": mtime }})
                db.tags.update({"tagname": request.args['tag'][0]},
                                {"$set": {"update": time.time()}})

            return json.dumps(reply)
        except:
            log.err()
            return resource.ErrorPage(status=500,
                                      brief='Error while deleting file!',
                                      detail='').render(request)

class Purge(resource.Resource):

    def render_POST(self, request):
        '''
        post response method for the Purge resource
        hostname:port/Purge
        '''
        if not LocalAuth.auth(request.getUser(), request.getPassword()):
            return LocalAuth.ReturnError(request)

        try:
            log.msg('Processing Purge request for file %s tag %s'
                    % (request.args['file'][0], request.args['tag'][0]))
            reply = []
            files = db.files.find_one({"tagname": request.args['tag'][0],
                                       "filename": request.args['file'][0]})
            if files != []:
                db.files.remove(files)

            return json.dumps(reply)
        except:
            log.err()
            return resource.ErrorPage(status=500,
                                      brief='Error while purging file!',
                                      detail='').render(request)


class PageNotFoundError(resource.Resource):

    def render_GET(self, request):
        return 'Page Not Found!'

#to make the process of adding new views less static
VIEWS = {
    'ListTags': ListTags(),
    'TagInfo': TagInfo(),
    'ListFiles': ListFiles(),
    'ListSubscriptions': ListSubscriptions(),
    'Subscribe': Subscribe(),
    'Unsubscribe': Unsubscribe(),
    'Get': Get(),
    'Put': Put(),
    'Delete': Delete(),
    'Purge': Purge()
}

if __name__ == '__main__':
    root = Root()
    for viewName, className in VIEWS.items():
        root.putChild(viewName, className)
    log.startLogging(sys.stdout)
    log.msg('Starting server: %s' % str(datetime.now()))
    server = server.Site(root)
    reactor.listenTCP(8000, server)  # #@UndefinedVariable
    reactor.run()  # #@UndefinedVariable
